/*
 * cache.c
 *
 *  Created on: 29/10/2011
 *      Author: utn_so
 */

#include "cache.h"
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <time.h>
//#include "functionsFAT.h"
#include "basicFunctions.h"
#include "rwBlocks.h"
#include "../../General/Stream/stream.h"

uint32_t Cache_Size;
uint32_t MAX_BLOCKS_IN_CACHE;

typedef struct {
	BLOQUE bloque;
	STREAM buffer;
	double timeLastUse;
} CACHE;

typedef struct Nodo_CACHE {
	CACHE cache[32]; //cubre el tamaño maximo de la cache pero solo se asigna cuando corresponde
	char * fileName;
	int32_t instances;
	struct Nodo_CACHE *nxt;
} Nodo_CACHE;

typedef struct List_CACHE {
	Nodo_CACHE * list;
	int32_t count;
} List_CACHE;

List_CACHE listCache;

void List_CACHE_Create()
{
	listCache.list = NULL;
	listCache.count = 0;
}

void List_CACHE_Add(const char * aFileName)
{
	int32_t i;
	Nodo_CACHE * post;
	Nodo_CACHE * ant;
	Nodo_CACHE * newNodo = (Nodo_CACHE *) malloc(sizeof(Nodo_CACHE));
	newNodo->fileName = (char *)calloc(strlen(aFileName) + 1, sizeof(char));
	strcpy(newNodo->fileName, aFileName);
	newNodo->instances = 1;
	newNodo->nxt = NULL;
	for(i = 0; i < MAX_BLOCKS_IN_CACHE; i++) {
		STREAM_Init(&(newNodo->cache[i].buffer));
		newNodo->cache[i].bloque = 0;
	}

	//Inserta ordenado
	if (listCache.list == NULL)
		listCache.list = newNodo;
	else {
		post = listCache.list;
		ant = post;
		while((post != NULL) && (strcasecmp(post->fileName, aFileName) < 0)) {
			ant = post;
			post = post->nxt;
		}
		newNodo->nxt = post;
		if (ant != post)
			ant->nxt = newNodo;
		else //va en la 1ra posicion
			listCache.list = newNodo;
	}
	listCache.count++;
}

void List_CACHE_Delete(const char * aFileName)
{
	Nodo_CACHE * post;
	Nodo_CACHE * ant;
	int32_t i;
	if (listCache.list == NULL)
		return;
	else {
		post = listCache.list;
		ant = post;
		while((post != NULL) && (strcasecmp(post->fileName, aFileName) != 0)) {
			ant = post;
			post = post->nxt;
		}
		if (post != ant)
			ant->nxt = post->nxt;
		else //Es el primero
			listCache.list = post->nxt;

		for(i = 0; i < MAX_BLOCKS_IN_CACHE; i++) {
			if (post->cache[i].bloque != 0)
				STREAM_Free(&(post->cache[i].buffer));
		}

		free(post->fileName);
		free(post);
		listCache.count--;
	}
}

void List_CACHE_GetPointer(const char * aFileName, Nodo_CACHE ** out)
{
	(*out) = listCache.list;
	while(((*out) != NULL) && (strcasecmp((*out)->fileName, aFileName) != 0))
		(*out) = (*out)->nxt;
}

int Cache_Disable()
{
	return Cache_Size == 0;
}

void Cache_Initialize(uint32_t size)
{
	if (!(size >= 4*1024 && size <= 32*1024 && size % 2 == 0)) { //Si el size no es un valor valido deshabilito la cache
		Cache_Size = 0;
	}
	else {
		Cache_Size = size;
		if (SizeOfBloque > 0)
			MAX_BLOCKS_IN_CACHE = size / SizeOfBloque;
		else
			MAX_BLOCKS_IN_CACHE = 0; //nunca deberia entrar por aca (se supone que se leyo el boot sector)
	}
	List_CACHE_Create();
}

void Cache_AddFile(const char * fileName)
{
	if (Cache_Disable()) return;
	Nodo_CACHE * aux;
	List_CACHE_GetPointer(fileName, &aux);
	if (aux != NULL)
		aux->instances++;
	else
		List_CACHE_Add(fileName);
}

void Cache_RemoveFile(const char * fileName)
{
	if (Cache_Disable()) return;
	Nodo_CACHE * aux = NULL;
	List_CACHE_GetPointer(fileName, &aux);
	if (aux != NULL) {
		aux->instances--;
		if (aux->instances == 0)
			List_CACHE_Delete(fileName); //Si no quedan mas archivos abiertos libera la memoria de la cache de ese archivo
	}
}

int Cache_ExistsBlock(Nodo_CACHE * nodo,BLOQUE aBlock)
{
	int32_t i = 0;
	if (nodo == NULL) return -1;
	while((i < MAX_BLOCKS_IN_CACHE) && (nodo->cache[i].bloque != aBlock))
		i++;
	if (i < MAX_BLOCKS_IN_CACHE)
		return i; //existe
	else
		return -1; //no existe
}

int Cache_ReplaceData(Nodo_CACHE * nodo)
{
	//LRU
	if (nodo == NULL) return -1;
	int32_t i = 0,
			result = -1;
	double older = nodo->cache[0].timeLastUse;
	while(i < MAX_BLOCKS_IN_CACHE) {
		if(nodo->cache[i].timeLastUse <= older) {
			older = nodo->cache[i].timeLastUse;
			result = i;
		}
		i++;
	}
	return result;
}

int Cache_Write(const char * fileName, char * buffer, BLOQUE aBlock)
{
	if (Cache_Disable()) return -1;
	struct timeval time;
	int32_t Replace = 0; //Boolean
	Nodo_CACHE * aux = NULL;
	int32_t i;
	List_CACHE_GetPointer(fileName, &aux);
	if(aux == NULL) return -1; //Solo ocurriria si no se inicio la cache del archivo cuando se abrio
	i = Cache_ExistsBlock(aux, aBlock);
	if (i == -1) {
		i = Cache_ExistsBlock(aux, 0); //busco un bloque libre
		if (i != -1) { //es un nuevo bloque
			STREAM_Alloc(&(aux->cache[i].buffer), SizeOfBloque);
			aux->cache[i].buffer.length = SizeOfBloque;
		}
		else {
			i = Cache_ReplaceData(aux);
			Replace = 1;
		}
	}

	if (i != -1 && aux != NULL) {
		if(Replace)
			writeBlock(aux->cache[i].bloque, (char *)aux->cache[i].buffer.data); //antes de reemplazar el bloque se actualiza
		aux->cache[i].bloque = aBlock;
		memcpy(aux->cache[i].buffer.data, buffer, SizeOfBloque);
		gettimeofday(&time, NULL);
		aux->cache[i].timeLastUse = (time.tv_sec * 1000) + (time.tv_usec/1000.0);
		return 0;
	}
	else
		return -1;
}

int Cache_Read(const char * fileName, char * bufferOut, BLOQUE aBlock)
{
	if (Cache_Disable()) return -1;
	struct timeval time;
	Nodo_CACHE * aux = NULL;
	int32_t i = 0;
	List_CACHE_GetPointer(fileName, &aux);
	if(aux == NULL) return -1; //Solo ocurriria si no se inicio la cache del archivo cuando se abrio
	i = Cache_ExistsBlock(aux, aBlock);
	if (aux != NULL && (i != -1)) {
		memcpy(bufferOut, aux->cache[i].buffer.data, SizeOfBloque);
		gettimeofday(&time, NULL);
		aux->cache[i].timeLastUse = (time.tv_sec * 1000) + (time.tv_usec/1000.0);
		return 0;
	}
	else
		return -1;
}

void Cache_Dump()
{
	int32_t nroBloque;
	int32_t i;
	char fileName[300];
	getWorkingDirectory(fileName);
	strcat(fileName, "cache_dump.txt");
	FILE * file_cache_dump = fopen(fileName, "a");
	if (file_cache_dump == NULL) {
		return;
	}
	time_t aTime;
	struct tm * timeinfo;
	time (&aTime);
	timeinfo = localtime (&aTime);
	char Timestamp[50 + 1];

	strftime (Timestamp, 50, "%Y.%m.%d %H:%M:%S",timeinfo);
	fprintf(file_cache_dump, "\nTimestamp: %s\n", Timestamp);
	fprintf(file_cache_dump, "Tamanio de Bloque de Cache: %d Kb\n", Cache_Size / 1024);
	fprintf(file_cache_dump, "Cantidad de Bloques de Cache: %d\n", listCache.count);
	if (listCache.count == 0) {
		fflush(file_cache_dump);
		return;
	}
	Nodo_CACHE *aux;
	aux = listCache.list;
	nroBloque = 1;
	while(aux != NULL) {
		for(i = 0; i < MAX_BLOCKS_IN_CACHE; i++) {
			if (aux->cache[i].bloque != 0) {
				fprintf(file_cache_dump, "\nContenido de Bloque de Cache %d:\n", nroBloque);
				fwrite(aux->cache[i].buffer.data, sizeof(char), aux->cache[i].buffer.length, file_cache_dump);
				nroBloque++;
			}
		}
		aux = aux->nxt;
	}

	fflush(file_cache_dump);
	return;
}

int Cache_SetListBlocks(const char * fileName, BLOQUE * aBlockList)
{
	if (Cache_Disable()) return -1;
	Nodo_CACHE * aux = NULL;
	int32_t i,
			j = 0;
	List_CACHE_GetPointer(fileName, &aux);
	if(aux == NULL) return -1; //Solo ocurriria si no se inicio la cache del archivo cuando se abrio
	for(i = 0; i < MAX_BLOCKS_IN_CACHE; i++){
		if (aux->cache[i].bloque != 0) {
			aBlockList[j] = aux->cache[i].bloque;
			j++;
		}
	}
	return 0;
}

int Cache_Flush(const char * path)
{
	BLOQUE blocksList[32];
	int Result = 0,
		i;
	char * buffer;

	if (Cache_Disable()) return 0;

	for(i = 0; i < 32; i++)
		blocksList[i] = 0;
	buffer = (char *)calloc(SizeOfBloque, sizeof(char));
	Cache_SetListBlocks(path, blocksList);
	if(blocksList[0] == 0) Result = -1;
	if (Result != -1) {
		i = 0;
		while (blocksList[i] != 0) {
			Cache_Read(path, buffer, blocksList[i]);
			writeBlock(blocksList[i], buffer);
			i++;
		}
	}

	free(buffer);
	return Result;
}
